#ifndef __MCS__MATH_FUNCTION_COLLECTION__HPP
#define __MCS__MATH_FUNCTION_COLLECTION__HPP

#include "FunctionFactory.hpp"
#define _USE_MATH_DEFINES
#include <cmath>

/// Collection of math functions
class MathFunctionCollection : public FunctionCollection
{
public:
	virtual void registerFunctions(FunctionFactory * factory)
	{
		factory->insertFunction("abs", &functionAbs<int>);
		factory->insertFunction("abs", &functionAbs<double>);
		factory->insertFunction("min", &functionMin<int>);
		factory->insertFunction("min", &functionMin<double>);
		factory->insertFunction("max", &functionMax<int>);
		factory->insertFunction("max", &functionMax<double>);
		factory->insertFunction("bound", &functionBound<int>);
		factory->insertFunction("bound", &functionBound<double>);
		factory->insertFunction("compare", &functionCompare1);
		factory->insertFunction("compare", &functionCompare2);

		factory->insertFunction("real", &functionReal);
		//TODO: add rounding functions

		factory->insertFunction("acos", &functionACos);
		factory->insertFunction("asin", &functionASin);
		factory->insertFunction("atan", &functionATan);
		factory->insertFunction("atan2", &functionATan2);
		factory->insertFunction("cos", &functionCos);
		factory->insertFunction("cosh", &functionCosH);
		factory->insertFunction("exp", &functionExp);
		factory->insertFunction("fmod", &functionFMod);
		factory->insertFunction("log", &functionLog);
		factory->insertFunction("log10", &functionLog10);
		factory->insertFunction("pow", &functionPow1);
		factory->insertFunction("pow", &functionPow2);
		factory->insertFunction("sin", &functionSin);
		factory->insertFunction("sinh", &functionSinH);
		factory->insertFunction("tan", &functionTan);
		factory->insertFunction("tanh", &functionTanH);
		factory->insertFunction("sqr", &functionSqr);
		factory->insertFunction("sqrt", &functionSqRt);
		factory->insertFunction("ceil", &functionCeil);
		factory->insertFunction("floor", &functionFloor);
		factory->insertFunction("getE", &functionGetE);
		factory->insertFunction("getPi", &functionGetPi);
		factory->insertFunction("fuzzyEq", &functionFuzzyEq1);
		factory->insertFunction("fuzzyEq", &functionFuzzyEq2);
		factory->insertFunction("fuzzyCmp", &functionFuzzyCmp1);
		factory->insertFunction("fuzzyCmp", &functionFuzzyCmp2);
		
		factory->insertFunction("rand", &functionRand);
		factory->insertFunction("randf", &functionRandF);
	}
private:
	template<class T> static T functionAbs(T a) { return a < 0 ? -a : a; }
	template<class T> static T functionMin(T a, T b) { return a < b ? a : b; }
	template<class T> static T functionMax(T a, T b) { return a < b ? b : a; }
	template<class T> static T functionBound(T a, T x, T b) { return functionMax(a, functionMin(x, b)); }

	static int functionCompare1(int a, int b) { return (a == b) ? 0 : (a < b ? -1 : +1); }
	static int functionCompare2(String const & a, String const & b) { return a.compare(b); }

	static double functionReal(int x) { return x; }

	static double functionACos(double x) { return acos(x); }
	static double functionASin(double x) { return asin(x); }
	static double functionATan(double x) { return atan(x); }
	static double functionATan2(double y, double x) { return atan2(y, x); }
	static double functionCos(double x) { return cos(x); }
	static double functionCosH(double x) { return cos(x); }
	static double functionExp(double x) { return exp(x); }
	static double functionFMod(double x, double y) { return fmod(x, y); }
	static double functionLog(double x) { return log(x); }
	static double functionLog10(double x) { return log10(x); }
	static double functionPow1(double x, double y) { return pow(x, y); }
	static double functionPow2(double x, int n) { return pow(x, n); }
	static double functionSin(double x) { return sin(x); }
	static double functionSinH(double x) { return sinh(x); }
	static double functionTan(double x) { return tan(x); }
	static double functionTanH(double x) { return tanh(x); }
	static double functionSqr(double x) { return x * x; }
	static double functionSqRt(double x) { return sqrt(x); }
	static double functionCeil(double x) { return ceil(x); }
	static double functionFloor(double x) { return floor(x); }

	static double functionGetE() { return M_E; }
	static double functionGetPi() { return M_PI; }

	static double functionRadToDeg(double x) { return x * 57.29577951308233; }
	static double functionDegToRad(double x) { return x * 0.0174532925199433; }


	static bool functionFuzzyEq1(double a, double b, double eps)
	{
		return fabs(a - b) <= eps * std::min(fabs(a), fabs(b));
	}

	static bool functionFuzzyEq2(double a, double b)
	{
		return functionFuzzyEq1(a, b, 0.000000000001);
	}

	static int functionFuzzyCmp1(double a, double b, double eps)
	{
		if(functionFuzzyEq1(a ,b, eps))
			return 0;
		return a < b ? -1 : +1;
	}

	static int functionFuzzyCmp2(double a, double b)
	{
		if(functionFuzzyEq2(a, b))
			return 0;
		return a < b ? -1 : +1;
	}

	static int functionRand() { return rand(); }
	static double functionRandF() { return double(rand()) / (double(RAND_MAX) + 1); }

};

#endif //__MCS__MATH_FUNCTION_COLLECTION__HPP
